package svc

import (
	"fmt"
	"github.com/casbin/casbin/v2"
	redisadapter "github.com/casbin/redis-adapter/v3"
	"github.com/zeromicro/go-zero/core/stores/cache"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"github.com/zeromicro/go-zero/core/syncx"
	"github.com/zeromicro/go-zero/rest"
	"golang.org/x/time/rate"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"os"
	"time"
	"wms/internal/config"
	"wms/internal/middleware"
	"wms/model"
)

type ServiceContext struct {
	Config              config.Config
	GlobalLimit         rest.Middleware
	IpLimit             rest.Middleware
	DB                  *gorm.DB
	Cache               cache.Cache
	Enforcer            *casbin.SyncedEnforcer
	UserModel           model.UserModel
	RoleModel           model.RoleModel
	ApiModel            model.ApiModel
	MenuModel           model.MenuModel
	RoleMenuModel       model.RoleMenuModel
	DepartmentModel     model.DepartmentModel
	SupplierModel       model.SupplierModel
	ClientModel         model.ClientModel
	MaterialsModel      model.MaterialsModel
	ProductModel        model.ProductModel
	StockInModel        model.StockInModel
	StockInDetailModel  model.StockInDetailModel
	StockOutModel       model.StockOutModel
	StockOutDetailModel model.StockOutDetailModel
	InboundInvoiceModel model.InboundInvoiceModel
	ProductPriceModel   model.ProductPriceModel
}

func NewServiceContext(c config.Config) *ServiceContext {
	//1.db初始化
	db, err := gorm.Open(mysql.Open(c.Database.MySQL), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info),
	})
	if err != nil {
		fmt.Printf("[Fatal]数据库初始化:%s\n", err.Error())
		os.Exit(1)
	}

	//2.casbin初始化
	//2.1 casbin 数据库连接
	//adapter, err := gormadapter.NewAdapter("mysql", c.Casbin.MySQL)
	adapter, err := redisadapter.NewAdapterWithPassword("tcp", c.Casbin.Redis, c.Casbin.RedisPass)
	if err != nil {
		panic(fmt.Sprintf("casbin数据库连接：%s", err.Error()))
	}
	//2.2 casbin model
	enforcer, err := casbin.NewSyncedEnforcer(c.Casbin.Conf, adapter)
	if err != nil {
		panic("enforcer初始化失败:" + err.Error())
	}

	//enforcer.EnableLog(true)

	//2.3 casbin auto load
	enforcer.StartAutoLoadPolicy(time.Minute)

	//QPS限流
	limit := rate.Every(20 * time.Millisecond) //每20毫秒
	//第一个参数是 r Limit。代表每秒可以向 Token 桶中产生多少 token。Limit 实际上是 float64 的别名。
	//第二个参数是 b int。b 代表 Token 桶的容量大小。
	// 20ms 往桶中放一个 Token。本质上也就是一秒钟产生 50 个。
	qpsLimit := rate.NewLimiter(limit, 100)
	//AllowN 方法表示，
	//截止到某一时刻，目前桶中数目是否至少为 n 个，
	//满足则返回 true，同时从桶中消费 n 个 token。
	//反之返回不消费 Token，false。

	//IP限流
	ipLimit := make(map[string]*rate.Limiter)

	return &ServiceContext{
		Config:              c,
		GlobalLimit:         middleware.GlobalLimiterMiddleware(qpsLimit),
		IpLimit:             middleware.IpLimiterMiddleware(ipLimit, 100),
		DB:                  db,
		Cache:               cache.New(c.CacheRedis, syncx.NewSingleFlight(), cache.NewStat(""), model.ErrNotFound),
		Enforcer:            enforcer,
		UserModel:           model.NewUserModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		RoleModel:           model.NewRoleModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		ApiModel:            model.NewApiModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		MenuModel:           model.NewMenuModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		RoleMenuModel:       model.NewRoleMenuModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		DepartmentModel:     model.NewDepartmentModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		SupplierModel:       model.NewSupplierModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		ClientModel:         model.NewClientModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		MaterialsModel:      model.NewMaterialsModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		ProductModel:        model.NewProductModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		StockInModel:        model.NewStockInModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		StockInDetailModel:  model.NewStockInDetailModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		StockOutModel:       model.NewStockOutModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		StockOutDetailModel: model.NewStockOutDetailModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		InboundInvoiceModel: model.NewInboundInvoiceModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
		ProductPriceModel:   model.NewProductPriceModel(sqlx.NewMysql(c.Database.MySQL), c.CacheRedis),
	}
}
